package icu.guodapeng.gateway.component

import cn.hutool.core.convert.Convert
import icu.guodapeng.gateway.domain.user.enum.AuthConstant
import icu.guodapeng.gateway.domain.user.enum.RedisConstant
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.http.HttpMethod
import org.springframework.security.authorization.AuthorizationDecision
import org.springframework.security.authorization.ReactiveAuthorizationManager
import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.web.server.authorization.AuthorizationContext
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono
import org.springframework.util.AntPathMatcher
import org.springframework.util.PathMatcher
import java.util.ArrayList
import javax.annotation.Resource


/**
 * 鉴权管理器，用于判断是否有资源的访问权限
 */
@Component
class AuthorizationManager : ReactiveAuthorizationManager<AuthorizationContext> {

    @Resource
    lateinit var redisTemplate: RedisTemplate<String, Any>

    override fun check(mono: Mono<Authentication>, context: AuthorizationContext): Mono<AuthorizationDecision> {
        val request = context.exchange.request
        // 对应跨域的预检请求直接放行
        if (request.method == HttpMethod.OPTIONS) {
            return Mono.just(AuthorizationDecision(true))
        }

        // 从 Redis 中获取当前路径可访问角色列表
        var authorities: List<String> = ArrayList()
        val resourceRolesMap: Map<String, List<String>> =
            redisTemplate.opsForHash<String, List<String>>().entries(RedisConstant.RESOURCE_ROLES_MAP.value)
        val iterator: Iterator<Any> = resourceRolesMap.keys.iterator()
        val pathMatcher: PathMatcher = AntPathMatcher()
        while (iterator.hasNext()) {
            val pattern = iterator.next() as String
            // TODO 这里感觉效率低
            if (pathMatcher.match(pattern, request.uri.path)) {
                authorities = Convert.toList(
                    String::class.java,
                    redisTemplate.opsForHash<String, List<String>>()
                        .get(RedisConstant.RESOURCE_ROLES_MAP.value, pattern)
                )
                break
            }
        }
        authorities = authorities.map { "${AuthConstant.AUTHORITY_PREFIX.value}${it}" }

        // 认证通过且角色匹配的用户可访问当前路径
        return mono
            .filter(Authentication::isAuthenticated)
            .flatMapIterable(Authentication::getAuthorities)
            .map(GrantedAuthority::getAuthority)
            .any(authorities::contains)
            .map(::AuthorizationDecision)
            .defaultIfEmpty(AuthorizationDecision(false))
    }
}
